﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MugenInjection.Core;
using MugenInjection.Interface;
using System.Reflection;

namespace MugenInjection.Infrastructure
{
    /// <summary>
    /// An internal container that manages and resolves <see cref="IBinding"/>s.
    /// </summary>
    public class BindingCollection : DisposableObject, IBindingCollection
    {
        #region Fields

        private static readonly IList<IBinding> EmptyList = new IBinding[0];
        private readonly List<IBinding> _bindings;
        private readonly List<IBindingBuilder> _bindingsBuilders;

        private readonly List<IBinding> _genericDefinition = new List<IBinding>();
        private readonly CacheCollection<Type, List<IBinding>> _cacheBindings;

        private readonly object _locker;
        private bool _isNewExist;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="BindingCollection"/> class.
        /// </summary>
        public BindingCollection()
        {
            _locker = new object();
            _bindings = new List<IBinding>();
            _bindingsBuilders = new List<IBindingBuilder>();
            _cacheBindings = new CacheCollection<Type, List<IBinding>>(40); 
            BindingBuilders = new ReadOnlyCollection<IBindingBuilder>(_bindingsBuilders);
        }

        #endregion

        #region Implementation of IEnumerable

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>1</filterpriority>
        public IEnumerator<IBinding> GetEnumerator()
        {
            lock (_locker)
            {
                if (_isNewExist)
                    RefreshCollection();
                return _bindings.GetEnumerator();
            }
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region Methods

        private void RefreshCollection()
        {
            for (int index = 0; index < _bindingsBuilders.Count; index++)
            {
                var builder = _bindingsBuilders[index];
                if (builder.IsConfiguring) continue;
                _bindingsBuilders.Remove(builder);
                AddBinding(builder.Build());
                index--;
            }
            _isNewExist = _bindingsBuilders.Count != 0;
        }

        private void OnAddedBinding(IBinding obj)
        {
            Action<IBinding> handler = AddedBinding;
            if (handler != null) handler(obj);
        }

        private void OnRemovedBinding(IBinding obj)
        {
            Action<IBinding> handler = RemovedBinding;
            if (handler != null) handler(obj);
        }

        private void OnAddedBindingBuilder(IBindingBuilder obj)
        {
            var handler = AddedBindingBuilder;
            if (handler != null) handler(obj);
        }

        private void OnRemovedBindingBuilder(IBindingBuilder obj)
        {
            var handler = RemovedBindingBuilder;
            if (handler != null) handler(obj);
        }

        #endregion

        #region Implementation of IBindingCollection

        /// <summary>
        /// Gets the number of elements contained in the <see cref="IBindingCollection"/>.
        /// </summary>
        /// <returns>
        /// The number of elements contained in the <see cref="IBindingCollection"/>.
        /// </returns>
        public int Count
        {
            get
            {
                lock (_locker)
                {
                    if (_isNewExist)
                        RefreshCollection();
                    return _bindings.Count;
                }
            }
        }

        /// <summary>
        /// Gets a collection of all <see cref="IBindingBuilder"/> in current <see cref="IBindingCollection"/>.
        /// </summary>
        public ReadOnlyCollection<IBindingBuilder> BindingBuilders { get; private set; }

        /// <summary>
        /// Searches all the <see cref="IBinding"/> satisfying the specified <see cref="IBindingContext"/>.
        /// </summary>
        /// <param name="context">The specified <see cref="IBindingContext"/>.</param>
        /// <returns>An instances of <see cref="IBinding"/>.</returns>
        public IEnumerable<IBinding> WhereCanResolve(IBindingContext context)
        {
            lock (_locker)
            {
                if (_isNewExist)
                    RefreshCollection();
                var bindings = _cacheBindings[context.Service];
                if (bindings == null)
                    bindings = _genericDefinition;
                for (int i = 0; i < bindings.Count; i++)
                {
                    IBinding binding = bindings[i];
                    if (!binding.CanResolve(context)) continue;
                    yield return binding;
                }
            }
        }

        /// <summary>
        /// Searches all the <see cref="IBinding"/> satisfying the specified <see cref="Type"/>.
        /// </summary>
        /// <param name="service">The specified <see cref="Type"/>.</param>
        /// <returns>An instances of <see cref="IBinding"/>.</returns>
        public IList<IBinding> WhereServiceEqual(Type service)
        {
            lock (_locker)
            {
                if (_isNewExist)
                    RefreshCollection();
                List<IBinding> result = _cacheBindings[service];
                if (result != null)
                    return result.ToArray();
                return EmptyList;
            }
        }

        /// <summary>
        /// Adds the specified <see cref="IBinding"/>.
        /// </summary>
        /// <param name="binding">The specified binding.</param>
        public void AddBinding(IBinding binding)
        {
            lock (_locker)
            {
                if (_bindings.Contains(binding))
                    throw new ArgumentException("The binding is already in the collection.");
                _bindings.Add(binding);
                foreach (var service in binding.Services)
                {
                    List<IBinding> result = _cacheBindings.GetOrAdd(service, () => new List<IBinding>());
                    if (!result.Contains(binding))
                        result.Add(binding);
#if NETFX_CORE
                    if (service.GetTypeInfo().IsGenericTypeDefinition && !_genericDefinition.Contains(binding))
#else
                    if (service.IsGenericTypeDefinition && !_genericDefinition.Contains(binding))
#endif

                        _genericDefinition.Add(binding);
                }
            }
            OnAddedBinding(binding);
        }

        /// <summary>
        /// Removes the specified <see cref="IBinding"/>.
        /// </summary>
        /// <param name="binding">The specified binding.</param>
        public bool RemoveBinding(IBinding binding)
        {
            bool isRemove;
            lock (_locker)
            {
                if (_isNewExist)
                    RefreshCollection();
                isRemove = _bindings.Remove(binding);
                _genericDefinition.Remove(binding);
            }
            if (isRemove)
            {
                binding.Dispose();
                foreach (var service in binding.Services)
                {
                    List<IBinding> bindings = _cacheBindings[service];
                    if (bindings != null)
                        bindings.Remove(binding);
                }
                OnRemovedBinding(binding);
            }
            return isRemove;
        }

        /// <summary>
        /// Adds the specified <see cref="IBindingBuilder"/>.
        /// </summary>
        /// <param name="bindingBuilder">The specified binding builder.</param>
        public void AddBindingBuilder(IBindingBuilder bindingBuilder)
        {
            lock (_locker)
            {
                _bindingsBuilders.Add(bindingBuilder);
                _isNewExist = true;
            }
            OnAddedBindingBuilder(bindingBuilder);
        }

        /// <summary>
        /// Removes the specified <see cref="IBindingBuilder"/>.
        /// </summary>
        /// <param name="bindingBuilder">The specified binding builder.</param>
        public bool RemoveBindingBindingBuilder(IBindingBuilder bindingBuilder)
        {
            bool remove;
            lock (_locker)
            {
                remove = _bindingsBuilders.Remove(bindingBuilder);
            }
            if (remove)
                OnRemovedBindingBuilder(bindingBuilder);
            return remove;
        }

        /// <summary>
        /// Occurs after add specified <see cref="IBinding"/>.
        /// </summary>
        public event Action<IBinding> AddedBinding;

        /// <summary>
        /// Occurs after remove specified <see cref="IBinding"/>.
        /// </summary>
        public event Action<IBinding> RemovedBinding;

        /// <summary>
        /// Occurs after add specified <see cref="IBindingBuilder"/>.
        /// </summary>
        public event Action<IBindingBuilder> AddedBindingBuilder;

        /// <summary>
        /// Occurs after remove specified <see cref="IBindingBuilder"/>.
        /// </summary>
        public event Action<IBindingBuilder> RemovedBindingBuilder;

        #endregion

        #region Overrides of DisposableObject

        /// <summary>
        /// Releases resources held by the object.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing && !IsDisposed)
            {
                while (_bindingsBuilders.Count != 0)
                    RemoveBindingBindingBuilder(_bindingsBuilders[0]);
                while (_bindings.Count != 0)
                    RemoveBinding(_bindings[0]);
                _cacheBindings.Clear();
            }
            base.Dispose(disposing);
        }

        #endregion
    }
}